﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Ue.Core.FileSystems;

namespace Ue.Eagle.Runtime.FileSystems.Dependencies
{
    public class PluginDependenciesManager : IPluginDependenciesManager
    {
        private const string BasePath = "Dependencies";
        private const string FileName = "dependencies.compiled.xml";

        private readonly IAppDataFolder appDataFolder;

        public PluginDependenciesManager(IAppDataFolder appDataFolder)
        {
            this.appDataFolder = appDataFolder;
        }

        private string PersistencePath
        {
            get { return this.appDataFolder.Combine(BasePath, FileName); }
        }

        public void StoreDependencies(IEnumerable<DependencyDescriptor> dependencyDescriptors, Func<DependencyDescriptor, string> fileHashProvider)
        {
            var newDocument = this.CreateDocument(dependencyDescriptors, fileHashProvider);
            var previousDocument = ReadDocument(PersistencePath);
            if (!XNode.DeepEquals(newDocument.Root, previousDocument.Root))
            {
                this.WriteDescriptors(newDocument, this.PersistencePath);
            }
        }

        public IEnumerable<string> GetVirtualPathDependencies(string pluginId)
        {
            var descriptor = this.GetDescriptor(pluginId);
            
            if(descriptor!=null && this.IsSupportedLoader(descriptor.LoaderName))
            {
                yield return this.appDataFolder.CombineToAppPath(this.PersistencePath);
            }
        }

        public ActivatedPluginDescriptor GetDescriptor(string pluginId)
        {
            return this.LoadDescriptors().FirstOrDefault(p => StringComparer.OrdinalIgnoreCase.Equals(p.PluginId, pluginId));
        }

        public IEnumerable<ActivatedPluginDescriptor> LoadDescriptors()
        {
            return this.ReadDescriptors(this.PersistencePath);
        }

        private XDocument CreateDocument(IEnumerable<DependencyDescriptor> dependencyDescriptors, Func<DependencyDescriptor, string> fileHashProvider)
        {
            Func<string, XName> ns = (name => XName.Get(name));

            var elements=dependencyDescriptors.Select(d=>
                new XElement(ns("Dependency"),
                    new XElement(ns("PluginId"), d.Name),
                    new XElement(ns("LoaderName"), d.LoaderName),
                    new XElement(ns("VirtualPath"), d.VirtualPath),
                    new XElement(ns("Hash"), fileHashProvider(d))
                )
            );

            return new XDocument(new XElement(ns("Dependencies"), elements.ToArray()));
        }

        private void WriteDescriptors(XDocument document,string path)
        {
            using (var stream = this.appDataFolder.OpenFile(path))
            {
                document.Save(stream, SaveOptions.None);
            }
        }

        private IEnumerable<ActivatedPluginDescriptor> ReadDescriptors(string path)
        {
            Func<string, XName> ns = name => XName.Get(name);
            Func<XElement, string, string> elem = (e, name) => e.Element(ns(name)).Value;

            var document = this.ReadDocument(path);

            return document
                .Elements(ns("Dependencies"))
                .Elements(ns("Dependency"))
                .Select(e =>
                    new ActivatedPluginDescriptor()
                    {
                        PluginId = elem(e, "PluginId"),
                        VirtualPath = elem(e, "VirtualPath"),
                        LoaderName = elem(e, "LoaderName"),
                        Hash = elem(e, "Hash")
                    }
                );
        }

        private XDocument ReadDocument(string persistancePath)
        {
            if (!this.appDataFolder.FileExists(persistancePath))
                return new XDocument();

            try
            {
                using (var stream = this.appDataFolder.OpenFile(persistancePath))
                {
                    return XDocument.Load(stream);
                }
            }
            catch (Exception)
            {
                return new XDocument();
            }
        }

        private bool IsSupportedLoader(string loaderName)
        {
            return loaderName == "DynamicExtensionLoader" 
                || loaderName == "PrecompiledExtensionLoader";
        }
    }
}
